﻿using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using Manatee.Trello;
using Manatee.Trello.ManateeJson;
using Manatee.Trello.WebApi;

namespace AIC_Notification
{
    class TrelloProcessorAIC
    {
        public string Program = "";
        public string CacheFileName = "";
        public string TempCacheFileName = "";
        public string BoardNamePattern = "";

        // Varible to keep track of which board/list/card we are working on
        public Manatee.Trello.Board ProcessingBoard = null;
        public Manatee.Trello.List ProcessingList = null;
        public Manatee.Trello.Card ProcessingCard = null;

        public void Initialize(string TrelloAppKey, string TrelloUserToken)
        {
            TrelloAuthorization.Default.AppKey = TrelloAppKey;
            TrelloAuthorization.Default.UserToken = TrelloUserToken;
            var serializer = new ManateeSerializer();
            TrelloConfiguration.Serializer = serializer;
            TrelloConfiguration.Deserializer = serializer;
            TrelloConfiguration.JsonFactory = new ManateeFactory();
            TrelloConfiguration.RestClientProvider = new WebApiClientProvider();
        }

        public void Uninitialize()
        {
            TrelloProcessor.Flush();
        }

        public void SetProcessingContext(Manatee.Trello.Board Board)
        {
            ProcessingBoard = Board;
            ProcessingList = null;
            ProcessingCard  = null;

            if (ProcessingBoard != null)
                Global.SysUtil.Log("Board: " + ProcessingBoard.Name);
            else
                Global.SysUtil.Log("Board is null", 2);
        }

        public void SetProcessingContext(Manatee.Trello.List List)
        {
            ProcessingList = List;
            ProcessingCard = null;

            if (ProcessingList != null)
                Global.SysUtil.Log("List: " + ProcessingList.Name);
            else
                Global.SysUtil.Log("List is null", 2);
        }

        public void SetProcessingContext(Manatee.Trello.Card Card)
        {
            ProcessingCard = Card;

            if (ProcessingCard != null)
                Global.SysUtil.Log("Card: " + ProcessingCard.Name);
            else
                Global.SysUtil.Log("List is null", 2);
        }

        public bool BoardFilter(string Program, int Year, string City, string StudentName)
        {
            // bool filterProgram = false;
            bool filterYear = false;
            bool filterCity = false;
            bool filterStudentName = false;
/*
            if (Global.config.ProgramFilter != "")
                filterProgram = (Program == Global.config.ProgramFilter);
            else
                filterProgram = true;
*/
            if (Global.config.YearFilter != 0)
                filterYear = (Year == Global.config.YearFilter);
            else
                filterYear = true;

            if (Global.config.CityFilter != "")
                filterCity = (City == Global.config.CityFilter);
            else
                filterCity = true;

            if (Global.config.StudentFilter != "")
                filterStudentName = (StudentName == Global.config.StudentFilter);
            else
                filterStudentName = true;

            return filterYear && filterCity && filterStudentName;
        }

        public bool MatchBoardWithStudent(ref AIC_Application Application, IList<Student> Students)
        {
            bool StudentFound = false;

            foreach (var student in Students)
            {
                if (student.Match(Application.Name, Application.City))
                {
                    Application.StudentObject = student;
                    student.TrelloId = Application.Id;
                    StudentFound = true;
                    break;
                }
            }

            if (StudentFound)
                return true;
            else
            {
                Global.SysUtil.Log(String.Format("Cannot match board and student object: {0}", Application.Name), 2);
                return false;
            }
        }

        public bool ConnectBoardwithCRM(IList<Student> Students)
        {
            bool AllMatched = true;

            if (!Global.ReparseStudentID)
                return true;

            Global.SysUtil.Log(String.Format("{0} boards on Trello", Manatee.Trello.Member.Me.Boards.Count()), 1);

            foreach (var board in Member.Me.Boards)
            {
                if (board.IsClosed != true) // not board closed
                {
                    Global.SysUtil.Log("Board: " + board.Name);
                    ProcessingBoard = board;

                    // Try to validate board name
                    Regex r = new Regex(BoardNamePattern, RegexOptions.None, TimeSpan.FromMilliseconds(150));
                    Match m = r.Match(board.Name);
                    if (m.Success)
                    {
                        // If not SOF or application year
                        if ((m.Groups["Program"].Value != Program) || ((Global.config.YearFilter != 0) && (Int32.Parse(m.Groups["ApplicationYear"].Value) != Global.config.YearFilter)))
                            continue;

                        // Filter out right program and year
                        bool FoundStudent = false;

                        Global.SysUtil.Log(String.Format("ID Board: {0} from {1}", m.Groups["StudentName"].Value.Trim(), m.Groups["City"].Value));

                        foreach (var student in Students)
                        {
                            if (student.Match(m.Groups["StudentName"].Value.Trim(), m.Groups["City"].Value))
                            {
                                student.TrelloId = board.Id;
                                student.Program = m.Groups["Program"].Value;
                                if (student.ApplicationYear != Int32.Parse(m.Groups["ApplicationYear"].Value))
                                    Global.SysUtil.Log(String.Format("Student: {0}, Board Year {1} does not match CRM Year {2}", m.Groups["StudentName"].Value.Trim(), Int32.Parse(m.Groups["ApplicationYear"].Value), student.ApplicationYear), 2);
                                student.PT = m.Groups["AcademicConsultant1"].Value;
                                student.ST = m.Groups["AcademicConsultant2"].Value;
                                student.EC = m.Groups["EducationConsultant"].Value;

                                FoundStudent = true;
                                break;
                            }
                        }

                        if (FoundStudent == false)
                        {
                            Global.InputErrors.Add(new Error(Error.Owner.admin, String.Format("No student matching this board: {0}", board.Name)));
                            AllMatched = false;
                        }
                    }
                }
            }

            // Log error for students not found
            foreach (var student in Students)
            {
                if (student.TrelloId == "")
                {
                    Global.InputErrors.Add(new Error(Error.Owner.admin, String.Format("Trello Board Not Found: {0} from {1}", student.Name, student.GetCity())));
                    AllMatched = false;
                }
            }

            return AllMatched;
        }

        public virtual void AddStudentToLists()
        {
        }

        public virtual void AddStudentToTeacher(string TeacherName, AIC_Application student)
        {
        }

        public int CardChangeInBoard(string TrelloBoardID)
        {
            int CardChange = 0;

            if (TrelloBoardID != "")
            {
                var board = new Manatee.Trello.Board(TrelloBoardID);
                var query = SearchFor.EditedWithinDay();
                var results = new Search(query, 100, SearchModelType.Cards, new Manatee.Trello.Contracts.IQueryable[] { board });
                CardChange = results.Cards.Count();
                Global.SysUtil.Log(String.Format("{0} has {1} card changes", board.Name, CardChange), 1);
            }

            return CardChange;
        }

        public bool Cache()
        {
            Global.SysUtil.Log("Load Board from Cache");
            return Global.SysUtil.ReadBoardsFromJson(Global.TempDirectory + CacheFileName);
        }

        public virtual void SetUpdate(ref Student ApplyingStudent, bool HasUpdate)
        {
        }

        public void MarkUpdate(ref Student ApplyingStudent, bool HasUpdate)
        {
            // Set update only if Trello board is found
            if (ApplyingStudent.TrelloId != "")
            {
                SetUpdate(ref ApplyingStudent, HasUpdate);

                if (HasUpdate)
                {
                    Statistics.TotalBoard += 1;
                }
            }
        }

        public virtual bool GetUpdate(Student ApplyingStudent)
        {
            return false;
        }

        public virtual void ReadBoard(Student ApplyingStudent)
        {           
        }

        private void MarkApplicationForUpdate(RunType App_RunType, IList<Student> Students)
        {
            // Reparse Board ID or use Json
            if (!ConnectBoardwithCRM(Students))
                Global.SysUtil.Log("Not all students matched", 2);

            // Mark boards for processing
            Statistics.TotalBoard = 0;
            foreach (var aic_student in Students)
            {
                Student CurrentStudent = aic_student;

                if (BoardFilter(aic_student.Program, aic_student.ApplicationYear, aic_student.GetCity(), aic_student.Name))
                {
                    // Update all
                    if (App_RunType == RunType.full)
                    {
                        MarkUpdate(ref CurrentStudent, true);
                    }
                    // Update for board changed last 24 hours
                    else if (App_RunType == RunType.update)
                    {
                        if (aic_student.TrelloId != "")
                        {
                            int CardChange = CardChangeInBoard(aic_student.TrelloId);
                            MarkUpdate(ref CurrentStudent, CardChange > 0 ? true : false);
                        }
                    }
                }
            }

            Global.SysUtil.Log(String.Format("Total Board to Process: {0}", Statistics.TotalBoard), 1);

            Uninitialize();
        }

        private void ReadBoards(RunType App_RunType, IList<Student> Students)
        {
            int RECORDS_IN_BATCH = 5;
            int MAX_RETRY = 5;

            // Mark begin of procesing with statistics
            Statistics.StartTime = Global.Now();
            Global.SysUtil.Log(String.Format("Begin Trello Updating: {0:F}", Statistics.StartTime), 1);

            // Load data from cache
            if (App_RunType == RunType.update || App_RunType == RunType.cache)
            {
                if (Cache() != true)
                    App_RunType = RunType.full; // if corrupt cache, then force full read.
            }

            MarkApplicationForUpdate(App_RunType, Students);

            Statistics.ProcessingBoard = 0;

            List<Student> StudentBatch = new List<Student>();
            int BatchCount = 0;
            int ExceptionCount = 0;
            int BatchProcessingBoard = 0;
            Global.SysUtil.WriteBoardsToJson(Global.TempDirectory + TempCacheFileName);

            while (true)
            {
                // Find Next Batch to Process
                string NextBatchStudentName = "";
                foreach (var aic_student in Students)
                {
                    if (GetUpdate(aic_student) && aic_student.TrelloId != "")
                    {
                        StudentBatch.Add(aic_student);
                        BatchCount += 1;
                        NextBatchStudentName += NextBatchStudentName == "" ? aic_student.Name : ", " + aic_student.Name;
                    }

                    if (BatchCount == RECORDS_IN_BATCH)
                        break;
                }

                // If not more to process, break.
                if (BatchCount == 0)
                    break;

                Global.SysUtil.Log(String.Format("Next Batch: {0}", NextBatchStudentName), 1);

                // Processing Stage: Begin
                try
                {
                    BatchProcessingBoard = Statistics.ProcessingBoard;

                    // Process board
                    foreach (var aic_student in StudentBatch)
                    {
                        Statistics.ProcessingBoard += 1;
                        Global.SysUtil.Log(String.Format("Begin Reading Board: {0}", aic_student.Name), 1);
                        DateTime BoardBeginTime = DateTime.Now;
                        ReadBoard(aic_student);
                        DateTime BoardEndTime = DateTime.Now;
                        TimeSpan BoardProcessTime = BoardEndTime - BoardBeginTime;
                        Global.SysUtil.Log(String.Format("End Reading Board: {0} - {1}m {2}s", aic_student.Name, BoardProcessTime.Minutes, BoardProcessTime.Seconds), 1);
                    }

                    // Write Students Object to Json
                    Global.SysUtil.WriteBoardsToJson(Global.TempDirectory + TempCacheFileName);

                    // Mark entry as done and clearn up
                    foreach (var aic_student in StudentBatch)
                    {
                        Student CurrentStudent = aic_student;
                        SetUpdate(ref CurrentStudent, false);
                    }
                    StudentBatch.Clear();
                    BatchCount = 0;

                    Uninitialize();
                    GC.Collect();
                }
                catch (Exception ex)
                {
                    if (ExceptionCount < MAX_RETRY)
                    {
                        StudentBatch.Clear();
                        BatchCount = 0;
                        Statistics.ProcessingBoard = BatchProcessingBoard; // Reset Processing Count

                        Uninitialize();
                        GC.Collect();

                        // Reload data from cache
                        if (Global.SysUtil.ReadBoardsFromJson(Global.TempDirectory + TempCacheFileName) == false)
                            throw ex; // Cache corrupted, not recoverable.

                        Global.SysUtil.Log(String.Format("Processing Exception: {0}", ExceptionCount), 2);
                        Global.SysUtil.Log(ex.ToString(), 2);
                        ExceptionCount += 1;
                        continue;
                    }
                    else
                    {
                        Global.SysUtil.Flush();
                        throw ex;
                    }
                }
            }

            // Processing Stage: End

            // Write Students Object to Json
            Global.SysUtil.WriteBoardsToJson(Global.TempDirectory + CacheFileName);

            // Also save file to output directory
            Global.SysUtil.WriteBoardsToJson(Global.OutputDirectory + CacheFileName);

            // Mark end of procesing with statistics
            Statistics.EndTime = Global.Now();
            Global.SysUtil.Log(String.Format("End Trello Updating: {0:F}", Statistics.EndTime), 1);
            Global.SysUtil.Log(String.Format("Time to read from Trello: {0}", (Statistics.EndTime - Statistics.StartTime)), 1);

            // Reset City and Teacher Lists
            for (int i = 0; i < CityDB.NoOfCities; i++)
                CityDB.Cities[i].Reset();
            foreach (var Teacher in TeacherDB.Teachers)
                Teacher.Reset();
        }

        public void ReadAttachments(Manatee.Trello.Card card, IList<Attachment> Attachments)
        {
            string FileExtension = "";

            // Read and store all card attachment information (without processing)
            foreach (var Attachment in card.Attachments)
            {
                FileExtension = Path.GetExtension(Attachment.Name);
                // Only add attachment if it is an essay - docx, doc, pages, pdf, txt)
                if (FileExtension.Equals(".docx", StringComparison.OrdinalIgnoreCase) ||
                    FileExtension.Equals(".doc", StringComparison.OrdinalIgnoreCase) ||
                    FileExtension.Equals(".pages", StringComparison.OrdinalIgnoreCase) ||
                    FileExtension.Equals(".rtf", StringComparison.OrdinalIgnoreCase) ||
                    FileExtension.Equals(".wps", StringComparison.OrdinalIgnoreCase) ||
                    FileExtension.Equals(".pdf", StringComparison.OrdinalIgnoreCase) ||
                    FileExtension.Equals(".txt", StringComparison.OrdinalIgnoreCase) ||
                    FileExtension.Equals(".zip", StringComparison.OrdinalIgnoreCase) ||
                    FileExtension.Equals("", StringComparison.OrdinalIgnoreCase))
                {
                    if (FileExtension.Equals("", StringComparison.OrdinalIgnoreCase))
                    {
                        if (Attachment.Name.StartsWith("http")) // Skip linked attachment
                            break;
                        else
                            Global.SysUtil.Log("No file extension");
                    }

                    if (FileExtension.Equals(".pdf", StringComparison.OrdinalIgnoreCase))
                        Global.SysUtil.Log("PDF file not editable");

                    AIC_Notification.Attachment NewAttachment = new AIC_Notification.Attachment();
                    NewAttachment.FileName = Attachment.Name;
                    NewAttachment.Url = Attachment.Url;
                    NewAttachment.CreationDate = (System.DateTime)Attachment.CreationDate;
                    NewAttachment.LastModifiedDate = (System.DateTime)Attachment.Date;
                    Global.SysUtil.Log(String.Format("Attachment: {0}", Attachment.Name));
                    Attachments.Add(NewAttachment);
                }
                else
                {
                    Global.SysUtil.Log(String.Format("Card {0}: Attachment \"{1}\" not essay file type", ProcessingCard.Name, Attachment.Name), 1, ProcessingBoard.Name, ProcessingList.Name);
                }
            }
        }

        public virtual void Process()
        {
            IList<Student> Students = null;

            // Set Student list to SOF or Guidance
            if (Statistics.ApplicationType == ApplicationType.sof)
                Students = Global.SOF_StudentDB.Students;
            else if (Statistics.ApplicationType == ApplicationType.guidance)
                Students = Global.Guidance_StudentDB.Students;

            switch (Statistics.RunType)
            {
                case RunType.full:
                    ReadBoards(RunType.full, Students);
                    break;
                case RunType.update:
                    ReadBoards(RunType.update, Students);
                    break;
                case RunType.cache:
                    Cache();
                    break;
                case RunType.skip:
                    break;
                default:
                    ReadBoards(RunType.full, Students);
                    break;
            }

            AddStudentToLists();
        }
    }
}
